package com.shakespace.artofandroid.chapter12bitmapandcache

import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import android.util.LruCache
import android.widget.ImageView
import com.shakespace.artofandroid.R
import com.shakespace.firstlinecode.global.TAG
import kotlinx.android.synthetic.main.activity_gallery.view.*
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.ThreadFactory
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger


private const val TAG = "ImageLoader"
const val MESSAGE_POST = 1

/**
 * created by  shakespace
 * 2021/4/12  22:01
 */
class ImageLoader(ctx: Context) {

    var isDiskLruCacheCreated = false
    var context: Context = ctx.applicationContext

    val imageResizer = ImageResizer()
    private var memoryCache: LruCache<String, Bitmap>
    lateinit var diskLruCache: DiskLruCache

    init {
        val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
        val cacheSize = maxMemory / 8

        //  初始化 LruCache , 容量为最大内存的 1/8
        memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
            override fun sizeOf(key: String, bitmap: Bitmap): Int {
                return bitmap.rowBytes * bitmap.height / 1024
            }
        }

        val diskCacheDir: File = getDiskCacheDir(context, "bitmap")
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs()
        }

        // 创建文件夹，初始化DiskLruCache
        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                diskLruCache =
                    DiskLruCache.open(diskCacheDir, getAppVersion(context), 1, DISK_CACHE_SIZE)
                isDiskLruCacheCreated = true
            } catch (e: IOException) {
                Log.e(TAG, "${e.localizedMessage} ")
            }

        }
    }

    private val mMainHandler: Handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            val result: LoaderResult = msg.obj as LoaderResult
            val imageView: ImageView = result.imageView
            val uri: String = result.uri
            if (uri == imageView.getTag(TAG_KEY_URI)) {
                imageView.setImageBitmap(result.bitmap)
            } else {
                Log.w(TAG, "set image bitmap, but url has changed, ignored!")
            }
        }
    }

    private class LoaderResult(var uri: String, var imageView: ImageView, var bitmap: Bitmap)

    //--------------------------创建缓存相关 Start------------------------------------
    /**
     * 获取可用空间
     */
    private fun getUsableSpace(path: File): Long {
        return path.usableSpace
    }

    fun getDiskCacheDir(context: Context, uniqueName: String): File {
        val cachePath: String
        cachePath =
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()
            ) {
                context.externalCacheDir!!.path
            } else {
                context.cacheDir.path
            }
        return File(cachePath + File.separator.toString() + uniqueName)
    }

    //获取应用版本号，注意不同的版本号会清空缓存
    fun getAppVersion(context: Context): Int {
        try {
            val info = context.packageManager.getPackageInfo(
                context.packageName, 0
            )
            return info.versionCode
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
        return 1
    }
    //--------------------------创建缓存相关 Ended------------------------------------

    fun bindBitmap(url: String, imageView: ImageView, reqWidth: Int = 0, reqHeight: Int = 0) {
        // 先给imageview设置tag
        imageView.setTag(TAG_KEY_URI, url)
        val bitmap = loadBitmapFromMemCache(url)
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap)
            return
        }

        // 如果为null， 异步加载
        val loadBitmapTask = Runnable {
            val bitmap = loadBitmap(url, reqWidth, reqHeight)
            if (bitmap != null) {
                val result = LoaderResult(url, imageView, bitmap)
                mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result)
                    .sendToTarget()
            }
        }

        // 执行
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask)
    }

    /**
     * 加载图片
     * 1. 先从内存缓存获取
     * 2. 再检查本地磁盘
     * 3. 再尝试从网络获取
     * 4. 如果网络获取失败，检查本地缓存是否开启【如果空间不足不会开启】，尝试直接从URL转换
     */
    fun loadBitmap(uri: String, reqWidth: Int, reqHeight: Int): Bitmap? {
        var bitmap = loadBitmapFromMemCache(uri)
        if (bitmap != null) {
            Log.d(TAG, "loadBitmap: loadBitmapFromMemCache $uri")
            return bitmap
        }

        try {
            // 同时会检查内存缓存中是否存在
            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight)
            if (bitmap != null) {
                Log.d(TAG, "loadBitmap: loadBitmapFromDiskCache $uri")
                return bitmap
            }
            // 写入磁盘缓存之后，从磁盘读取
            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight)
            Log.d(TAG, "loadBitmap: loadBitmapFromHttp $uri")
        } catch (e: IOException) {
            Log.e(TAG, "loadBitmap: ${e.localizedMessage} ")
        }

        // 没有磁盘缓存，直接从url读取
        if (bitmap == null && !isDiskLruCacheCreated) {
            bitmap = downloadBitmapFromUrl(uri)
        }

        return bitmap
    }

    /**
     * 从缓存中加载图片
     */
    private fun loadBitmapFromMemCache(uri: String): Bitmap? {
        val key = hashKeyFromUrl(uri)
        return getBitmapFromMemCache(key)
    }

    /**
     * kotlin 抛出异常是用注解写的
     * 从disk 缓存中获取图片，同时检查内存中是否存在，无则同时添加到内存缓存中
     */
    @Throws(IOException::class)
    private fun loadBitmapFromDiskCache(url: String, reqWidth: Int, reqHeight: Int): Bitmap? {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.e(
                this.TAG,
                "loadBitmapFromDiskCache:  it is not recommended to load bitmap in main thread "
            )
        }

        var bitmap: Bitmap? = null
        val key = hashKeyFromUrl(url)
        val snapShot: DiskLruCache.Snapshot? = diskLruCache.get(key)
        if (snapShot != null) {
            val fileInputStream = snapShot.getInputStream(DISK_CACHE_INDEX) as FileInputStream
            bitmap = imageResizer.decodeSampleBitmapFromFileDescriptor(
                fileInputStream.fd,
                reqWidth,
                reqHeight
            )
            // 添加到缓存
            addBitmapToMemoryCache(key, bitmap);
        }
        return bitmap
    }

    /**
     * 将网络图片加载到 磁盘缓存， 同时调用从磁盘获取的方法【同时也会添加到内存缓存中】
     */
    @Throws(IOException::class)
    private fun loadBitmapFromHttp(url: String, reqWidth: Int, reqHeight: Int): Bitmap? {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw RuntimeException("can not visit network in from UI Thread")
        }
        val key: String = hashKeyFromUrl(url)
        val editor: DiskLruCache.Editor? = diskLruCache.edit(key)
        if (editor != null) {
            val outputStream: OutputStream = editor.newOutputStream(DISK_CACHE_INDEX)
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit()
            } else {
                editor.abort()
            }
        }
        diskLruCache.flush()
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight)
    }

    /**
     * 将图片写入磁盘缓存
     */
    private fun downloadUrlToStream(urlString: String, outputStream: OutputStream): Boolean {
        var urlConnection: HttpURLConnection? = null
        var out: BufferedOutputStream? = null
        var input: BufferedInputStream? = null
        try {
            val url = URL(urlString)
            // 打开链接， 开启输入流
            urlConnection = url.openConnection() as HttpURLConnection
            input = BufferedInputStream(
                urlConnection.getInputStream(),
                IO_BUFFER_SIZE
            )
            // 输出流是 前面用磁盘缓存的editor 创建的
            out = BufferedOutputStream(outputStream, IO_BUFFER_SIZE)
            var b: Int
            while (input.read().also { b = it } != -1) {
                out.write(b)
            }
            // 写入成功 则返回true，editor.commit() 后， 之后就可以从磁盘缓存中读取
            return true
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            urlConnection?.disconnect()
            try {
                out?.close()
                input?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        return false
    }

    /**
     * 直接解析网络输入流
     */
    private fun downloadBitmapFromUrl(urlString: String): Bitmap? {
        var bitmap: Bitmap? = null
        var connection: HttpURLConnection? = null
        var bis: BufferedInputStream? = null
        try {
            val url = URL(urlString)
            connection = url.openConnection() as HttpURLConnection
            bis = BufferedInputStream(
                connection.inputStream,
                IO_BUFFER_SIZE
            )
            bitmap = BitmapFactory.decodeStream(bis)
        } catch (e: IOException) {
            Log.e(TAG, "Error in downloadBitmap: $e")
        } finally {
            connection?.disconnect()
            try {
                bis?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        return bitmap
    }


    //----------------------添加到内存缓存和读取---------------------------------
    /**
     * 缓存bitmap到内存中，先检查对应的key是否有缓存对象，没有则添加
     * key 是处理过的uri， 通常是做MD5
     */
    private fun addBitmapToMemoryCache(key: String, bitmap: Bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            memoryCache.put(key, bitmap)
        }
    }

    private fun getBitmapFromMemCache(key: String): Bitmap? = memoryCache.get(key)
    //----------------------添加到内存缓存和读取 END---------------------------------

    /**
     * 考虑到直接使用URL作为DiskLruCache中LinkedHashMap的Key不太适合，
     * 因为图片URL中可能包含一些特殊字符，这些字符有可能在命名文件时是不合法的。
     * 其实最简单的做法就是将图片的URL进行MD5编码，编码后的字符串肯定是唯一的，
     * 并且只会包含0-F这样的字符，完全符合文件的命名规则。
     *
     * 该方法在写入磁盘和从磁盘读取时都需要用到，因为它是计算出索引的方法
     *
     * @param key 图片的url
     * @return MD5编码之后的key
     */
    fun hashKeyFromUrl(key: String): String {
        var cacheKey: String
        try {
            val mDigest: MessageDigest = MessageDigest.getInstance("MD5")
            mDigest.update(key.toByteArray())
            cacheKey = bytesToHexString(mDigest.digest())
        } catch (e: NoSuchAlgorithmException) {
            cacheKey = key.hashCode().toString()
        }
        return cacheKey
    }

    private fun bytesToHexString(bytes: ByteArray): String {
        val sb = StringBuilder()
        for (i in bytes.indices) {
            val hex = Integer.toHexString(0xFF and bytes[i].toInt())
            if (hex.length == 1) {
                sb.append('0')
            }
            sb.append(hex)
        }
        return sb.toString()
    }

    // 静态变量
    companion object {
        private val CPU_COUNT = Runtime.getRuntime().availableProcessors()
        private val CORE_POOL_SIZE = CPU_COUNT + 1
        private val MAXIMUM_POOL_SIZE = CPU_COUNT * 1 + 1
        private const val KEEP_ALIVE = 1L

        const val DISK_CACHE_SIZE = 1024 * 1024 * 50L
        const val IO_BUFFER_SIZE = 8 * 1024
        const val DISK_CACHE_INDEX = 0

        const val TAG_KEY_URI = R.id.grid_view

        const val MESSAGE_POST_RESULT = 1

        private val threadFactory = object : ThreadFactory {
            val count = AtomicInteger(1)
            override fun newThread(r: Runnable): Thread {
                return Thread(r, "ImageLoader # ${count.getAndIncrement()}")
            }
        }

        // 线程池
        val THREAD_POOL_EXECUTOR = ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
            KEEP_ALIVE, TimeUnit.SECONDS,
            LinkedBlockingDeque<Runnable>(),
            threadFactory
        )


        // 静态方法 创建
        fun build(context: Context): ImageLoader {
            return ImageLoader(context)
        }
    }
}